package com.dasberg.guice;

import com.google.inject.AbstractModule;
import org.apache.log4j.Logger;
import org.scannotation.AnnotationDB;
import org.scannotation.ClasspathUrlFinder;

import javax.servlet.*;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

/**
 * The Guice Auto register filter automagically registers all Guice Modules that have the {@link com.dasberg.guice.Module}
 * annotation present.
 * @author mischa
 */
public class GuiceAutoregFilter implements Filter {
    private static final Logger LOG = Logger.getLogger(GuiceAutoregFilter.class);
    private static final String JAR_DIRECTORY = "/WEB-INF/lib";
    private int minimumNumberOfClasspaths = 3;
    private String[] otherModules;

    /** {@inheritDoc}. */
    public void init(FilterConfig filterConfig) throws ServletException {
        try {
            minimumNumberOfClasspaths = Integer.parseInt(filterConfig.getInitParameter("minimumNumberOfClasspaths"));
        } catch (NumberFormatException e) {
            LOG.debug("minimumNumberOfClasspaths is not of type Integer. Using default instead.");
        } catch (NullPointerException e) {
            LOG.debug("No minimumNumberOfClasspaths found as init param. Using default instead.");
        }
        try {
            otherModules = filterConfig.getInitParameter("otherModules").split(",");
            for (String module : otherModules) {
                LOG.debug("Found other module: " + module);
            }
        } catch (IllegalArgumentException e) {
            LOG.debug("otherModules is not a comma seperated list. No other modules where injected");
        } catch (NullPointerException e) {
            LOG.debug("No otherModules found as init param.");
        }
        try {
            LOG.debug("Guice auto register filter initialization started.");
            Set<String> entities = new HashSet<String>();
            entities.addAll(getEntities(filterConfig));
            if (otherModules != null) {
                entities.addAll(Arrays.asList(otherModules));
            }
            if (entities != null) {
                LOG.debug("Found " + entities.size() + " Modules.");
                int i = 1;
                for (String entry : entities) {
                    try {
                        StaticInjector.registerModule((AbstractModule) Class.forName(entry).newInstance(), i == entities.size());
                    } catch (Exception e) {
                        LOG.error("Error while trying to inject module: " + entry + ".", e);
                    }
                    i++;
                }
            } else {
                LOG.debug("Found 0 Modules.");
            }
            LOG.debug("Guice auto register filter initialized.");
        } catch (IOException e) {
            LOG.error("Error while trying to scan classpath. ", e);
        }
    }

    /**
     * Gets a Set of module names.
     * @param filterConfig The filterConfig.
     * @return moduleNames The Set of module names.
     * @throws IOException The exception.
     */
    protected Set<String> getEntities(FilterConfig filterConfig) throws IOException {
        URL[] classPaths = findClassPaths(filterConfig.getServletContext());
        LOG.debug("Found " + classPaths.length + " classpaths");
        for (URL url : classPaths) {
            LOG.debug("Found url: " + url);
        }
        AnnotationDB db = new AnnotationDB();
        db.scanArchives(classPaths);
        Set<String> found = db.getAnnotationIndex().get(Module.class.getName());
        return found != null ? found : new HashSet<String>();
    }

    /**
     * Find all classpaths.
     * If there are less then 5 (I imagine that there are almost always 5 classpaths in a project),
     * we should get all the ClassPaths in the WEB-INF/lib dir.
     * @param servletContext The servlet context.
     * @return urls The URL's.
     */
    private URL[] findClassPaths(ServletContext servletContext) {
        URL[] classPaths = ClasspathUrlFinder.findClassPaths();

        if (classPaths.length <= minimumNumberOfClasspaths) {
            LOG.debug("Did not find the minimum number of classpaths specified.");
            Set libJars = getWebInfJars(servletContext);
            List<URL> urls = new ArrayList<URL>();
            for (Object jar : libJars) {
                try {
                    urls.add(servletContext.getResource((String) jar));
                } catch (MalformedURLException e) {
                    LOG.debug("Could not add jar '" + jar + "'.");
                }
            }
            classPaths = urls.toArray(new URL[urls.size()]);
        }
        return classPaths;
    }

    /**
     * Gets the jars from the WEB-INF/lib dir.
     * @param servletContext The servlet context.
     * @return jars The jars
     */
    protected Set getWebInfJars(ServletContext servletContext) {
        return servletContext.getResourcePaths(JAR_DIRECTORY);
    }

    /** {@inheritDoc}. */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, response);
    }

    /** {@inheritDoc}. */
    public void destroy() {
        StaticInjector.unregisterAllModules();
        LOG.debug("Guice auto register filter shutdown.");
    }
}